unit DataSetSerializeDSImpl;

interface

uses DB, superobject, FMTBcd;

type
  TDataSetSerialize = class
  private
    FDataSet: TDataSet;
    FOnlyUpdatedRecords: Boolean;
    FChildRecord: Boolean;
    /// <summary>
    ///   Creates a JSON object with the data from the current record of DataSet.
    /// </summary>
    /// <param name="ADataSet">
    ///   Refers to the DataSet that you want to export the record.
    /// </param>
    /// <returns>
    ///   Returns a JSON object containing the record data.
    /// </returns>
    /// <remarks>
    ///   Invisible or null fields will not be exported.
    /// </remarks>
    function DataSetToJSONObject(const ADataSet: TDataSet): ISuperObject;
    /// <summary>
    ///   Creates an array of JSON objects with all DataSet records.
    /// </summary>
    /// <param name="ADataSet">
    ///   Refers to the DataSet that you want to export the records.
    /// </param>
    /// <returns>
    ///   Returns a JSONArray with all records from the DataSet.
    /// </returns>
    /// <remarks>
    ///   Invisible or null fields will not be exported.
    /// </remarks>
    function DataSetToJSONArray(const ADataSet: TDataSet; const IsChild: Boolean =
      False): ISuperObject;
    /// <summary>
    ///   Encrypts a blob field in Base64.
    /// </summary>
    /// <param name="AField">
    ///   Refers to the field of type Blob or similar.
    /// </param>
    /// <returns>
    ///   Returns a string with the cryptogrammed content in Base64.
    /// </returns>
    function EncodingBlobField(const AField: TField): string;
    /// <summary>
    ///   Verifiy if a DataSet has detail dataset and if has child modification.
    /// </summary>
    function HasChildModification(const ADataSet: TDataSet): Boolean;
  public
    /// <summary>
    ///   Responsible for creating a new instance of TDataSetSerialize class.
    /// </summary>
    constructor Create(const ADataSet: TDataSet; const AOnlyUpdatedRecords: Boolean = False; const
      AChildRecords: Boolean = True);
    /// <summary>
    ///   Creates an array of JSON objects with all DataSet records.
    /// </summary>
    /// <returns>
    ///   Returns a JSONArray with all records from the DataSet.
    /// </returns>
    /// <remarks>
    ///   Invisible or null fields will not be generated.
    /// </remarks>
    function ToJSONArray: ISuperObject;
    /// <summary>
    ///   Creates a JSON object with the data from the current record of DataSet.
    /// </summary>
    /// <returns>
    ///   Returns a JSON object containing the record data.
    /// </returns>
    /// <remarks>
    ///   Invisible or null fields will not be generated.
    /// </remarks>
    function ToJSONObject: ISuperObject;
    /// <summary>
    ///   Responsible for exporting the structure of a DataSet in JSON Array format.
    /// </summary>
    /// <returns>
    ///   Returns a JSON array with all fields of the DataSet.
    /// </returns>
    /// <remarks>
    ///   Invisible fields will not be generated.
    /// </remarks>
    function SaveStructure: ISuperObject;
  end;

implementation

uses BooleanFieldTypes, DateUtils, SysUtils, ProvidersDataSetSerialize, TypInfo,
  ProvidersDataSetSerializeConstants, Classes, publicfun4d7,
  UpdatedStatusTypes;

{ TDataSetSerialize }

function TDataSetSerialize.ToJSONObject: ISuperObject;
begin
  Result := DataSetToJSONObject(FDataSet);
end;

function TDataSetSerialize.DataSetToJSONArray(const ADataSet: TDataSet; const
  IsChild: Boolean = False): ISuperObject;
var
  LBookMark: TBookmark;
begin
  Result := TSuperObject.Create(stArray);
  if ADataSet.IsEmpty then
    Exit;
  try
    LBookMark := ADataSet.GetBookmark;
    ADataSet.First;
    while not ADataSet.Eof do
    begin
      if IsChild and FOnlyUpdatedRecords then
        if (ADataSet.UpdateStatus = usUnmodified) and not
          (HasChildModification(ADataSet)) then
        begin
          ADataSet.Next;
          Continue;
        end;
      Result.AsArray.Add(DataSetToJSONObject(ADataSet));
      ADataSet.Next;
    end;
  finally
    if ADataSet.BookmarkValid(LBookMark) then
      ADataSet.GotoBookmark(LBookMark);
    ADataSet.FreeBookmark(LBookMark);
  end;
end;

function TDataSetSerialize.DataSetToJSONObject(const ADataSet: TDataSet):
  ISuperObject;
var
  i: Integer;
  LKey: string;
  LNestedDataSet: TDataSet;
  LDataSetDetails: TList;
  LField: TField;
begin
  Result := TSuperObject.Create;
  if not Assigned(ADataSet) or ADataSet.IsEmpty then
    Exit;
  for i := 0 to ADataSet.Fields.Count - 1 do
  begin
    LField := ADataSet.Fields[i];
    //  for LField in ADataSet.Fields do
    //  begin
    if (not LField.Visible) or LField.IsNull or TrimIsEmpty(LField.AsString) then
      Continue;
    LKey := LowerCase(LField.FieldName);
    case LField.DataType of
      ftBoolean:
        begin
          case TDataSetSerializeUtils.BooleanFieldToType(TBooleanField(LField)) of
            bfUnknown, bfBoolean:
              begin
                Result.B[LKey] := LField.AsBoolean;
              end;
          else
            begin
              Result.I[LKey] := LField.AsInteger;
            end;
          end;
        end;
      ftInteger, ftSmallint: //, ftShortint:
        begin
          Result.I[LKey] := LField.AsInteger;
        end;
      ftAutoInc:
        begin
          Result.S[LKey] := LField.AsString;
        end;
      ftLargeint:
        begin
          Result.I[LKey] := LField.AsInteger;
        end;
      ftFloat:
        begin
          Result.D[LKey] := LField.AsFloat;
        end;
      ftString, ftWideString, ftMemo: //, ftWideMemo:
        begin
          Result.S[LKey] := LField.AsString;
        end;
      ftDate, ftTimeStamp, ftDateTime, ftTime:
        begin
          Result.S[LKey] := DelphiDateTimeToISO8601Date(LField.AsDateTime);
          //      Result.AddPair(LKey, TJSONString.Create(DateToISO8601(LField.AsDateTime)));
        end;
      ftCurrency:
        begin
          Result.S[LKey] := FormatCurr('0.00##', LField.AsCurrency);
        end;
      ftFMTBcd, ftBCD:
        begin
          Result.D[LKey] := BcdToDouble(LField.AsBcd);
        end;
      ftDataSet:
        begin
          LNestedDataSet := TDataSetField(LField).NestedDataSet;
          Result.O[LKey] := DataSetToJSONArray(LNestedDataSet);
        end;
      ftGraphic, ftBlob: //, ftStream:
        begin
          Result.S[LKey] := EncodingBlobField(LField);
        end;
    else
      raise EDataSetSerializeException.CreateFmt(FIELD_TYPE_NOT_FOUND, [LKey]);
    end;
  end;
  if (FOnlyUpdatedRecords) and (FDataSet <> ADataSet) then
    Result.S[OBJECT_STATE] := TUpdateStatusToString(ADataSet.UpdateStatus);
  if FChildRecord then
  begin
    LDataSetDetails := TList.Create;
    try
      ADataSet.GetDetailDataSets(LDataSetDetails);
      for i := 0 to LDataSetDetails.Count - 1 do
      begin
        LNestedDataSet := TDataset(LDataSetDetails.Items[i]);
        //      end;
      //      for LNestedDataSet in LDataSetDetails do
      //      begin
//        if FOnlyUpdatedRecords then
//          TDataSet(LNestedDataSet).FilterChanges := [rtInserted, rtModified, rtDeleted,
//            rtUnmodified];
        if LNestedDataSet.RecordCount > 0 then
          Result.O[LowerCase(TDataSetSerializeUtils.FormatDataSetName(LNestedDataSet.Name))] :=
            DataSetToJSONArray(LNestedDataSet, True);
        //        if FOnlyUpdatedRecords then
        //          TDataSet(LNestedDataSet).FilterChanges := [rtInserted, rtModified, rtUnmodified];
      end;
    finally
      LDataSetDetails.Free;
    end;
  end;
end;

function TDataSetSerialize.EncodingBlobField(const AField: TField): string;
var
  LMemoryStream: TMemoryStream;
  LStringStream: TStringStream;
begin
  LMemoryStream := TMemoryStream.Create;
  try
    TBlobField(AField).SaveToStream(LMemoryStream);
    LMemoryStream.Position := 0;
    LStringStream := TStringStream.Create('');
    try
      Result := Base64Encode(LMemoryStream);
      //      TNetEncoding.Base64.Encode(LMemoryStream, LStringStream);
      //      Result := LStringStream.DataString;
    finally
      LStringStream.Free;
    end;
  finally
    LMemoryStream.Free;
  end;
end;

function TDataSetSerialize.HasChildModification(const ADataSet: TDataSet): Boolean;
var
  i: Integer;
  LDataSetDetails: TList;
  LNestedDataSet: TDataSet;
begin
  Result := False;
  LDataSetDetails := TList.Create;
  try
    ADataSet.GetDetailDataSets(LDataSetDetails);
    for i := 0 to LDataSetDetails.Count - 1 do
    begin
      LNestedDataSet := Tdataset(LDataSetDetails.Items[i]);
      //    for LNestedDataSet in LDataSetDetails do
  //    begin
      if not (LNestedDataSet is TDataSet) then
        Continue;
      try
        //        TDataSet(LNestedDataSet).FilterChanges := [rtInserted, rtModified, rtDeleted];
        if (TDataSet(LNestedDataSet).RecordCount > 0) or HasChildModification(LNestedDataSet) then
        begin
          Result := True;
          Break;
        end;
      finally
        //        TDataSet(LNestedDataSet).FilterChanges := [rtInserted, rtModified, rtUnmodified];
      end;
    end;
  finally
    LDataSetDetails.Free;
  end;
end;

function TDataSetSerialize.SaveStructure: ISuperObject;
var
  i: Integer;
  LField: TField;
  LJSONObject: ISuperObject;
begin
  Result := TSuperObject.Create(stArray);
  if FDataSet.FieldCount <= 0 then
    Exit;
  for i := 0 to FDataSet.Fields.count - 1 do
  begin
    LField := FDataSet.Fields[i];
    LJSONObject := TSuperObject.Create;
    LJSONObject.S['Alignment'] := GetEnumName(TypeInfo(TAlignment),
      Ord(LField.Alignment));
    LJSONObject.S['FieldName'] := LField.FieldName;
    LJSONObject.S['DisplayLabel'] := LField.DisplayLabel;
    LJSONObject.S['DataType'] := GetEnumName(TypeInfo(TFieldType), Integer(LField.DataType));
    LJSONObject.I['Size'] := LField.SIZE;
    LJSONObject.B['Key'] := pfInKey in LField.ProviderFlags;
    LJSONObject.S['Origin'] := LField.ORIGIN;
    LJSONObject.B['Required'] := LField.Required;
    LJSONObject.B['Visible'] := LField.Visible;
    LJSONObject.B['ReadOnly'] := LField.ReadOnly;
    LJSONObject.S['AutoGenerateValue'] := GetEnumName(TypeInfo(TAutoRefreshFlag),
      Integer(LField.AutoGenerateValue));
    Result.AsArray.Add(LJSONObject);
  end;
end;

constructor TDataSetSerialize.Create(const ADataSet: TDataSet; const AOnlyUpdatedRecords: Boolean =
  False; const AChildRecords: Boolean = True);
begin
  FDataSet := ADataSet;
  FOnlyUpdatedRecords := AOnlyUpdatedRecords;
  FChildRecord := AChildRecords;
end;

function TDataSetSerialize.ToJSONArray: ISuperObject;
begin
  Result := DataSetToJSONArray(FDataSet);
end;

end.
